package com.compiler;

import com.ast.*;
import com.ir.*;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

import com.asm.*;

public class IRGenerator {
    SysyFunc sysyFunc = new SysyFunc();
    List<Var> topDef = new ArrayList<>();
    List<FuncDefNode> funs = new ArrayList<>();
    List<Stmt> topStmts = new ArrayList<>();
    List<FloatIR> topFloats = new ArrayList<>();
    public IR generate(AST ast) {
        
        if(ast==null)
            return null;
        // 编译变量
        if (ast.getDecl() != null)
            for (DeclNode decl : ast.getDecl()) {
                topDef.addAll(compile(decl));
            }

        // 编译函数
        if (ast.getFuncDef() != null) {
            for (FuncDefNode node : ast.getFuncDef()) {
                funs.add(node);
                node.setIR(compile(node));
            }
        }
        //输出调试
        //getIRString();
        // for(Var var:vars){
        //     System.out.println(var.getName()+" "+var.getIntValue());
        // }
        return new IR(funs, topDef,topStmts,topFloats);
    }

    List<Stmt> stmts;
    List<Var> vars;
    List<Var> params;
    List<FloatIR> floats;
    LinkedList<Label> breakStack;
    LinkedList<Label> continueStack;
    List<Var> global;
    Stack<LocalScop>scops;
    LocalScop scop;
    LocalScop tmpscop;
    int maxnum;

    public List<Var> compile(DeclNode decl) {
        List<Var> tmpDef = new ArrayList<>();
        if (decl.getConstDecl() != null) {
            for (ConstDefNode node : decl.getConstDecl().getConstdef()) {
                String type = decl.getConstDecl().getType().getType();
                Var var = new Var(type, node.getName(), null, true,false, false);
                if(node.getConstExp().size()!=0)
                {
                    var.setArray(true);
                    var.setShape(compile_index(node.getConstExp()));
                    var.setValue_list(compile_value_const(node.getConstIni().getConstInitVals()));
                }
                else
                    {
                        var.setExpr(compile(node.getConstIni().getConstExp().getAddExp()));
                    }
                if(scop!=null)
                    scop.vars.add(var);
                tmpDef.add(var);
                if(stmts!=null)
                    stmts.add(new ExprStmt(var));
                else
                {
                    var.setGlobal(true);
                    topStmts.add(new ExprStmt(var));
                }
            }
        }
        if (decl.getVarDecl() != null) {
            for (VarDefNode node : decl.getVarDecl().getVarDef()) {
                String type = decl.getVarDecl().getType().getType();
                Var var = new Var(type, node.getName(), null, false, false, false);
                
                if (node.getConstExps().size() != 0) {
                    var.setArray(true);
                    var.setShape(compile_index(node.getConstExps()));
                    if(node.getInitVal()!=null)
                        var.setValue_list(compile_value_var(node.getInitVal().getInitVals()));
                } 
                    else if(node.getInitVal()!=null)
                        var.setExpr(compile(node.getInitVal().getExp()));
                    tmpDef.add(var);
                    if(scop!=null)
                        scop.vars.add(var);
                    if(stmts!=null)
                        stmts.add(new ExprStmt(var));
                    else
                    {
                        var.setGlobal(true);
                        topStmts.add(new ExprStmt(var));
                    }
                
            }
        }
        return tmpDef;
    }

    public List<Expr> compile_index(List<ConstExpNode> list){
        if(list==null)
            return null;
        List<Expr> exprs = new ArrayList<>();
        for(ConstExpNode node:list){
            exprs.add(compile(node.getAddExp()));
        }
        return exprs;
    }

    public List<Expr> compile_value_const(List<ConstInitValNode> node) {
        if(node==null)
            return null;
        List<Expr> exprs = new ArrayList<>();
        for (ConstInitValNode c : node) {
            if (c.getConstExp() != null)
                exprs.add(compile(c.getConstExp().getAddExp()));
            else {
                exprs.addAll(compile_value_const(c.getConstInitVals()));
            }
        }
        return exprs;
    }

    // 解决命名冲突问题
    public List<Expr> compile_value_var(List<InitValNode> node) {
        if(node==null)
            return null;
        List<Expr> exprs = new ArrayList<>();
        for (InitValNode c : node) {
            if (c.getExp() != null)
                exprs.add(compile(c.getExp()));
            else {
                exprs.addAll(compile_value_var(c.getInitVals()));
            }
        }
        return exprs;
    }

    public Func compile(FuncDefNode funcDef) {
        stmts = new ArrayList<>();
        vars = new ArrayList<>();
        breakStack = new LinkedList<>();
        continueStack = new LinkedList<>();
        floats = new ArrayList<>();
        params = new ArrayList<>();
        global = new ArrayList<>();
        scops = new Stack<>();
        maxnum=0;
        scop=new LocalScop();
        if (funcDef.getFuncFParams() != null) {
            for (FuncFParamNode node : funcDef.getFuncFParams().getFuncFParams()) {
                Var var=new Var(node.getType().getType(), node.getName(), null, false, node.isArray, false);
                var.isParam=true;
                if(node.getExps()!=null)
                {
                    List<Expr>shape=new ArrayList<>();
                    shape.add(new Int("", 0));
                    for(ExpNode exp:node.getExps()){
                        shape.add(compile(exp));
                    }
                    var.setArray(true);
                    var.setShape(shape);
                }
                params.add(var);
            }
        }

        for (BlockItemNode blockItem : funcDef.getBlock().getBlockItems()) {
            compile(blockItem);
        }
        return new Func(stmts, vars, params,floats, global,maxnum);
    }

    public void compile(BlockItemNode blockItem) {
        if (blockItem.getDecl() != null) {
            vars.addAll(compile(blockItem.getDecl()));
        } else {
            compile(blockItem.getStmt());
        }
    }

    public void compile(StmtNode stmt) {
        if (stmt.getlVal() != null) {
            stmts.add(new ExprStmt(new Assign(compile(stmt.getlVal()), compile(stmt.getExp()))));
        } else if (stmt.getExp() != null) {
            stmts.add(new ExprStmt(compile(stmt.getExp())));
        } else if (stmt.getBlockNode() != null) {
        // System.out.println(scop.id);
        // if(scop==null)
        //     System.out.println("scop is null");
        tmpscop=new LocalScop();
        tmpscop.father=scop;
        scops.push(scop);
        scop=tmpscop;
            for (BlockItemNode blockItem : stmt.getBlockNode().getBlockItems()) {
                compile(blockItem);
            }
        scop=scops.pop();
        // System.out.println(scop.id);
        } else if (stmt.getIfNode() != null) {
            compile(stmt.getIfNode());
        } else if (stmt.getWhileNode() != null) {
            compile(stmt.getWhileNode());
        } else if (stmt.getBreakNode() != null) {
            compile(stmt.getBreakNode());
        } else if (stmt.getContinueNode() != null) {
            compile(stmt.getContinueNode());
        } else if (stmt.getReturnNode() != null) {
            compile(stmt.getReturnNode());
        }
    }

    public void compile(IfNode ifNode) {
        Label thenLabel = new Label();
        Label elseLabel = new Label();
        Label endLabel = new Label();
        Expr cond = compile(ifNode.getCond().getlOrExp());
        if (ifNode.getElseStmt() == null) {
            cjump(cond, thenLabel, endLabel);
            label(thenLabel);
            compile(ifNode.getThenStmt());
            label(endLabel);
        } else {
            cjump(cond, thenLabel, elseLabel);
            label(thenLabel);
            compile(ifNode.getThenStmt());
            jump(endLabel);
            label(elseLabel);
            compile(ifNode.getElseStmt());
            label(endLabel);
        }
    }

    public FuncDefNode findF(String name) {
        for (FuncDefNode node : funs) {
            if (node.getName().equals(name))
                return node;
        }
        return null;
    }

    public Var find(String name) {
        LocalScop t=scop;
        while(t!=null){
            for (Var var : t.vars) {
                if (var.getName().equals(name)) {
                    //System.out.println(t.id+"+"+var.getName()+"+"+var.getIntValue());
                    return var;
                }
            }
            t=t.father;
        }
        if(params!=null)
        for (Var var : params) {
            if (var.getName().equals(name)) {
                return var;
            }
        }
        if(topDef!=null)
        for (Var var : topDef) {
            if (var.getName().equals(name)) {
                if(!global.contains(var))
                    global.add(var);
                return var;
            }
        }
        return null;
    }

    public Expr compile(LValNode node) {
        Var var=find(node.getName());
        if(node.getExps().size()==0)
        {
            return var;
        }
        else{
            List<Expr> exps=new ArrayList<>();
            for(ExpNode exp:node.getExps()){
                exps.add(compile(exp));
            }
            if(var==null)
                System.out.println(node.getName()+"is not defined");
             var.setIndex(exps);
             return var;
        }
    }

    public void compile(WhileNode whileNode) {
        Label begLabel = new Label();
        Label bodyLabel = new Label();
        Label endLabel = new Label();

        label(begLabel);
        cjump(compile(whileNode.getCond()), bodyLabel, endLabel);
        label(bodyLabel);
        pushContinue(begLabel);
        pushBreak(endLabel);
        compile(whileNode.getStmt());
        popBreak();
        popContinue();
        jump(begLabel);
        label(endLabel);
    }

    public void compile(BreakNode node) {
        try {
            if (breakStack.isEmpty()) {
                throw new Exception("break from out of loop");
            } else {
                jump(breakStack.getLast());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void compile(ContinueNode node) {
        try {
            if (continueStack.isEmpty()) {
                throw new Exception("continue from out of loop");
            } else {
                jump(continueStack.getLast());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void compile(ReturnNode node) {
        if (node.getExp() != null)
            stmts.add(new Return(compile(node.getExp())));
        else
            stmts.add(new Return(null));
    }

    // 编译
    int exprNestLevel = 0;

    public Expr compile(ExpNode node) {
        if(node==null)
            return null;
        exprNestLevel++;
        Expr e = compile(node.getAddExp());
        exprNestLevel--;
        return e;
    }

    private boolean isStatement() {
        return (exprNestLevel == 0);
    }

    public Expr compile(AddExpNode node) {
        if (node.getAddExp() != null)
            return new BinExp(compile(node.getAddExp()),compile(node.getMulExp()), node.getOp());
        else
            return compile(node.getMulExp());
    }

    public Expr compile(MulExpNode node) {
        if (node.getMulExp() != null)
            return new BinExp(compile(node.getMulExp()), compile(node.getUnaryExp()), node.getOp());
        else
            return compile(node.getUnaryExp());
    }

    public Expr compile(UnaryExpNode node) {
        if (node.getPrimaryExp() != null)
            return compile(node.getPrimaryExp());
        else if (node.getName() != null) {
            int index =sysyFunc.isSysyFunc(node.getName());
            if(index!=-1)
            {
                return new Call(new FuncDefNode(sysyFunc.getFuncType(index),node.getName()), compile(node.getFuncRParams()));
            }
            else
                {
                    FuncDefNode f = findF(node.getName());
                    if(f==null)
                        throw new Error("undefined function "+node.getName());
                    if(node.getFuncRParams()!=null && maxnum<node.getFuncRParams().getExps().size()){
                        maxnum=node.getFuncRParams().getExps().size();
                    }
                    return new Call(f, compile(node.getFuncRParams()));
                }
        } else
            return new UnaryExp(node.getOp(), compile(node.getUnaryExp()));
    }

    public List<Expr> compile(FuncRParamsNode node) {
        if(node==null)
            return new ArrayList<>();
        List<Expr> exprs = new ArrayList<>();
        if (node.getExps() != null)
            for (ExpNode exp : node.getExps()) {
                exprs.add(compile(exp));
            }
        return exprs;
    }

    public Expr compile(PrimaryExpNode node) {
        if (node.getExp() != null)
            return compile(node.getExp());
        else if (node.getNum() != null) {
            if (node.getNum().getType().equals("int"))
                {
                    return new Int("", node.getNum().getIntVal());
                }
            else
                {
                    FloatIR t= new FloatIR("", node.getNum().getFloatVal());
                    if(stmts==null)
                        topFloats.add(t);
                    else
                        floats.add(t);
                    return t;
                }
        } else {
            Var var=find(node.getlVal().getName());
            if (var == null) {
                throw new Error("undefined variable " + node.getlVal().getName());
            }
            if(var.isArray()){
                List<Expr> indexs = new ArrayList<>();
                for(ExpNode exp:node.getlVal().getExps()){
                    indexs.add(compile(exp));
                }
                var.setIndex(indexs);
            }
            return var;
        }
    }

    public Expr compile(CondNode node) {
        return compile(node.getlOrExp());
    }

    public Expr compile(LOrExpNode node) {
        if (node.getlOrExp() != null)
            return new BinExp(compile(node.getlOrExp()), compile(node.getlAndExp()), "||");
        else
            return compile(node.getlAndExp());
    }

    public Expr compile(LAndExpNode node) {
        if (node.getlAndExp() != null)
            return new BinExp(compile(node.getlAndExp()), compile(node.getEqExp()), "&&");
        else
            return compile(node.getEqExp());
    }

    public Expr compile(EqExpNode node) {
        if (node.getEqExp() != null)
            return new BinExp(compile(node.getEqExp()), compile(node.getRelExp()), node.getOp());
        else
            return compile(node.getRelExp());
    }

    public Expr compile(RelExpNode node) {
        if (node.getRelExp() != null)
            return new BinExp(compile(node.getRelExp()), compile(node.getAddExp()), node.getOp());
        else
            return compile(node.getAddExp());
    }

    // 语句编译
    private void label(Label label) {
        stmts.add(new LabelStmt(label));
    }

    private void jump(Label target) {
        stmts.add(new Jump(target));
    }

    private void cjump(Expr cond, Label thenLabel, Label elseLabel) {
        stmts.add(new CJump(cond, thenLabel, elseLabel));
    }

    private void pushBreak(Label label) {
        breakStack.add(label);
    }

    private void popBreak() {
        if (breakStack.isEmpty()) {
            throw new Error("unmatched push/pop for break stack");
        }
        breakStack.removeLast();
    }

    private void pushContinue(Label label) {
        continueStack.add(label);
    }

    private void popContinue() {
        if (continueStack.isEmpty()) {
            throw new Error("unmatched push/pop for continue stack");
        }
        continueStack.removeLast();
    }
    
    public void getIRString(){
        System.out.println("Global");
        // for(FloatIR t:topFloats){
        //     System.out.println(t.getValue());
        // }
        
        for(Stmt stmt:topStmts){
            System.out.println(stmt.getString());
        }
        for (FuncDefNode fun : funs) {
            // for(FloatIR t:fun.getFunc().getFloatIRs()){
            //     System.out.println(t.getValue());
            // }
            System.out.println(fun.getName());
            for (Stmt stmt : fun.getFunc().getStmts()) {
                System.out.println(stmt.getString());
            }
        }
    }
}
